import { fileURLToPath } from 'node:url'
import { parseArgs as nodeUtilParseArgs } from 'node:util'

import { $, cd, chalk, fs, glob, path, within } from 'zx'

export const TARBALL_DEST_DIRNAME = 'tarballs'

export const FRAMEWORK_PATH = fileURLToPath(
  new URL('../../../', import.meta.url),
)

export const IGNORE_EXTENSIONS = ['.DS_Store']

/**
 * Add to this array of strings, RegExps, or functions (whichever makes the most sense)
 * to ignore files that we don't want triggering package rebuilds.
 */
export const IGNORED = [
  /node_modules/,

  /packages\/codemods/,
  /packages\/create-redwood-app/,

  /dist/,

  /__fixtures__/,
  /__mocks__/,
  /__tests__/,
  /\.test\./,
  /jest.config.{js,ts}/,

  /README.md/,

  // esbuild emits meta.json files that we sometimes suffix.
  /meta.(\w*\.?)json/,

  /tsconfig.tsbuildinfo/,
  /tsconfig.build.tsbuildinfo/,
  /tsconfig.cjs.tsbuildinfo/,

  // The tarballs generated by `yarn build:pack`
  /redwoodjs-.*\.tgz$/,

  (filePath: string) => IGNORE_EXTENSIONS.some((ext) => filePath.endsWith(ext)),
]

export interface Options {
  projectPath: string
  watch: boolean
  verbose: boolean
}

export async function getOptions(): Promise<Options> {
  const { positionals, values } = nodeUtilParseArgs({
    allowPositionals: true,
    options: {
      verbose: {
        type: 'boolean',
        default: false,
        short: 'v',
      },
      watch: {
        type: 'boolean',
        default: false,
        short: 'w',
      },
    },
  })
  const [projectPath] = positionals

  const options: Options = {
    projectPath: projectPath ?? process.env.RWJS_CWD ?? '',
    watch: values.watch ?? false,
    verbose: values.verbose ?? false,
  }

  if (!options.projectPath) {
    throw new Error(
      [
        'Error: You have to provide the path to a Redwood project as',
        '',
        '  1. the first positional argument',
        '',
        chalk.gray('  yarn project:tarsync /path/to/redwood/project'),
        '',
        '  2. the `RWJS_CWD` env var',
        '',
        chalk.gray('  RWJS_CWD=/path/to/redwood/project yarn project:tarsync'),
      ].join('\n'),
    )
  }

  // This makes `projectPath` an absolute path and throws if it doesn't exist.
  options.projectPath = await fs.realpath(options.projectPath)

  return options
}

export async function buildTarballs() {
  await $`yarn nx run-many -t build:pack --exclude create-redwood-app`
}

export async function moveTarballs(projectPath: string) {
  const tarballDest = path.join(projectPath, TARBALL_DEST_DIRNAME)
  await fs.ensureDir(tarballDest)

  const tarballs = await glob(['./packages/**/*.tgz'])

  await Promise.all(
    tarballs.map((tarball) =>
      fs.move(tarball, path.join(tarballDest, path.basename(tarball)), {
        overwrite: true,
      }),
    ),
  )
}

export async function updateResolutions(projectPath: string) {
  const resolutions = (await $`yarn workspaces list --json`).stdout
    .trim()
    .split('\n')
    .map((line) => JSON.parse(line))
    // Filter out the root workspace.
    .filter(({ name }) => name)
    .reduce((resolutions, { name }) => {
      return {
        ...resolutions,
        // Turn a Redwood package name like `@redwoodjs/project-config` into `redwoodjs-project-config.tgz`.
        [name]: `./${TARBALL_DEST_DIRNAME}/${
          name.replace('@', '').replaceAll('/', '-') + '.tgz'
        }`,
      }
    }, {})

  const projectPackageJsonPath = path.join(projectPath, 'package.json')
  const projectPackageJson = await fs.readJSON(projectPackageJsonPath)

  await fs.writeJSON(
    projectPackageJsonPath,
    {
      ...projectPackageJson,
      resolutions: {
        ...projectPackageJson.resolutions,
        ...resolutions,
        ...(await getReactResolutions()),
      },
    },
    {
      spaces: 2,
    },
  )
}

export async function getReactResolutions() {
  const packageConfig = await fs.readJson(
    path.join(FRAMEWORK_PATH, 'packages/web/package.json'),
  )

  const react = packageConfig.peerDependencies.react
  const reactDom = packageConfig.peerDependencies['react-dom']

  if (!react || !reactDom) {
    throw new Error(
      "Couldn't find react or react-dom in @redwoodjs/web's peerDependencies",
    )
  }

  return {
    react,
    'react-dom': reactDom,
  }
}

export async function yarnInstall(projectPath: string) {
  await within(async () => {
    cd(projectPath)
    await $`yarn install`
  })
}
